package com.sky.service.impl;

import com.sky.dto.GoodsSalesDTO;
import com.sky.entity.Orders;
import com.sky.mapper.OrderMapper;
import com.sky.mapper.UserMapper;
import com.sky.service.ReportService;
import com.sky.service.WorkspaceService;
import com.sky.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.StringUtil;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class ReportServiceImpl implements ReportService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WorkspaceService workspaceService;

    /**
     * 营业额统计
     * @param begin
     * @param end
     * @return
     */
    @Override
    public TurnoverReportVO turnoverStatistics(LocalDate begin, LocalDate end) {
        // 1.准备日期列表数据 dateList--->如近7日:5-14，5-15...5-20
        List<LocalDate> dateList = getLocalDates(begin, end);
        log.info("营业额日期列表:{}",dateList);
        // 2.准备营业额列表数据 turnoverList
        List<Double> turnoverList = new ArrayList<>();
        //营业额= 订单状态已完成的订单金额
        //查询orders表，条件:状态-已完成，下单时间
        //通过循环遍历日期列表，对每个日期进行查询,获取当天的营业额
        dateList.forEach(date -> {
            Map map = new HashMap();
            map.put("status", Orders.COMPLETED);
            map.put("begin", LocalDateTime.of(date, LocalTime.MIN));//  开始时间 2025-07-28 00:00:00
            map.put("end", LocalDateTime.of(date, LocalTime.MAX)); //  结束时间 2025-07-28 23:59:59
            Double turnover = orderMapper.selectSumByMap(map);// 将构建好的数据传入进行查询订单表，获取营业额
            //进行判断如果当如营业额为null，则设置营业额为0
            turnover = turnover == null ? 0.0 : turnover;
            turnoverList.add(turnover);//  将营业额添加到营业额列表
            log.info("{}营业额:{}",date,turnover);

        });
        // 3.构造TurnoverReportV0对象并返回
        return TurnoverReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))//  将日期列表转换为字符串使用逗号分隔
                .turnoverList(StringUtils.join(turnoverList, ","))//  将营业额列表转换为字符串使用逗号分隔
                .build();
    }

    /**
     * (复用)获取指定日期范围内的日期列表  Ctrl+Alt+M 抽取为方法
     * @param begin
     * @param end
     * @return
     */
    private static List<LocalDate> getLocalDates(LocalDate begin, LocalDate end) {
        List<LocalDate> dateList = new ArrayList<>();//  准备一个日期列表，用于存放从 begin 到 end 的所有日期
        //  开始一个循环，从 begin 到 end，依次获取日期
        LocalDate temp = begin;
        // 循环插入日期数据
        while (!temp.isAfter(end)) {//  判断当前日期是否在结束日期之后
            dateList.add(temp);
            // 注意:小心死循环
            temp = temp.plusDays(1);
        }
        return dateList;
    }

    /**
     * 用户统计
     * @param begin
     * @param end
     * @return
     */
    @Override
    public UserReportVO userStatistics(LocalDate begin, LocalDate end) {
        //  1.准备日期列表数据,构造dateLists数据
        List<LocalDate> dateList = getLocalDates(begin, end);
        // 2.准备用户数据列表,构造newUserList数据
        List<Integer> newUserList = new ArrayList<>();
        // 3.totaluserList数据，总用户列表
        List<Integer> totalUserList = new ArrayList<>();
        dateList.forEach(date -> {
            //  查询当日的新增用户
            Map map = new HashMap();
            map.put("begin",LocalDateTime.of(date, LocalTime.MIN));//  开始时间为2025-07-28 00:00:00
            map.put("end", LocalDateTime.of(date, LocalTime.MAX));//  结束时间为2025-07-28 23:59:59
            Integer total = userMapper.selectCountByMap(map);//  查询总用户数
            newUserList.add(total);
            //相当于复用了selectCountByMap方法的查询,只需要将开始时间在设置为空就相当于查询了一直到当天结束时间的所有用户而不是某一天的用户
            //selectCountByMap里的sql语句会进行判断，如果开始时间不为空则查询开始时间到结束时间之间的用户，如果开始时间是空则查询到结束数据的所有用户
            //  查询总用户
            map.put("begin",null);
            Integer totalUser = userMapper.selectCountByMap(map);
            totalUserList.add(totalUser);
        });
        log.info("用户统计日期列表:{}",dateList);
        //4.构造UserReportV0对象并返回
        return UserReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))//  将日期列表转换为字符串使用逗号分隔
                .newUserList(StringUtils.join(newUserList, ","))//  将新增用户列表转换为字符串使用逗号分隔
                .totalUserList(StringUtils.join(totalUserList, ","))//  将总用户列表转换为字符串使用逗号分隔
                .build();
    }

    /**
     * 订单统计
     * @param begin
     * @param end
     * @return
     */
    @Override
    public OrderReportVO ordersStatistics(LocalDate begin, LocalDate end) {
        //  1.准备日期列表数据,构造dateLists数据
        List<LocalDate> dateList = getLocalDates(begin, end);
        //  2.准备订单数据列表,构造orderCountList数据
        List<Integer> orderCountList = new ArrayList<>();
        //  3.准备有效订单数据列表,构造validOrderCountList数据
        List<Integer> validOrderCountList = new ArrayList<>();
        //  准备订单总数
        Integer totalOrderCount = 0;
        //  准备有效订单总数
        Integer validsOrderCount = 0;
        //  准备订单完成率
        Double orderCompletionRate = 0.0;
        //  4.循环遍历日期列表，对每个日期进行查询
        for (LocalDate localDate : dateList) {
            //获取每日总订单列表数 ordercountList
            Map map = new HashMap();
            map.put("begin",LocalDateTime.of(localDate, LocalTime.MIN));
            map.put("end", LocalDateTime.of(localDate, LocalTime.MAX));
            Integer orderCount = orderMapper.selectCountByMap(map);//  查询订单数
            orderCountList.add(orderCount);
            //  获取每日有效订单列表数 validOrderCountList
            map.put("status",Orders.COMPLETED);
            Integer validOrderCount = orderMapper.selectCountByMap(map);//  查询有效订单数
            validOrderCountList.add(validOrderCount);
            //  5.计算订单总数
            totalOrderCount += orderCount;
            //  6.计算有效订单总数
            validsOrderCount += validOrderCount;
        }
        //  7.计算订单完成率
        orderCompletionRate = validsOrderCount.doubleValue() / totalOrderCount;
        log.info("订单总数{},有效订单总数{},订单完成率{}",totalOrderCount,validsOrderCount,orderCompletionRate);
        return OrderReportVO.builder()
                .dateList(StringUtils.join(dateList, ","))//  将日期列表转换为字符串使用逗号分隔
                .orderCountList(StringUtils.join(orderCountList, ","))//  将订单列表转换为字符串使用逗号分隔
                .validOrderCountList(StringUtils.join(validOrderCountList, ","))//  将有效订单列表转换为字符串使用逗号分隔
                .totalOrderCount(totalOrderCount)//  总订单数
                .validOrderCount(validsOrderCount)//  有效订单数
                .orderCompletionRate(orderCompletionRate)//  订单完成率
                .build();
    }

    /**
     * 销量排名top10
     * @param begin
     * @param end
     * @return
     */
    @Override
    public SalesTop10ReportVO top10(LocalDate begin, LocalDate end) {
        //1.构造nameList:商品名称列表
        List<String> nameList = new ArrayList<>();
        //2.构造numberList:商品销量列表
        List<Integer> numberList = new ArrayList<>();
        //  查询订单明细表order_detail+ 订单表orders, 条件:订单状态-已完成，下单时间
        //  添加查询数据
        Map map = new HashMap();
        map.put("begin",LocalDateTime.of(begin, LocalTime.MIN));//  开始时间为为前端传递的开始时间
        map.put("end", LocalDateTime.of(end, LocalTime.MAX));//  结束时间为为前端传递的结束时间
        map.put("status",Orders.COMPLETED);//  订单状态-已完成
        List<GoodsSalesDTO> orderDetailList = orderMapper.selectSalesTop10(map);
        //  3.遍历查询结果，将数据添加到nameList和numberList中
        for (GoodsSalesDTO orderDetail : orderDetailList) {
            nameList.add(orderDetail.getName());
            numberList.add(orderDetail.getNumber());
        }
        //  4.构造SalesTop10ReportVO对象并返回
        return SalesTop10ReportVO.builder()
                .nameList(StringUtils.join(nameList, ","))//  将名称列表转换为字符串使用逗号分隔
                .numberList(StringUtils.join(numberList, ","))//  将商品销量列表转换为字符串使用逗号分隔
                .build();
    }

    /**
     * 导出营业数据
     * @param response
     */
    @Override
    public void exportBusinessData(HttpServletResponse response) {
        //1:查询数据库，获取营业数据---查询最近30天的运营数据
        LocalDate begin = LocalDate.now().minusDays(30);//获取30天前的日期
        LocalDate end = LocalDate.now().minusDays(1);//获取昨天的日期
        //转换为LocalDateTime格式
        LocalDateTime beginTime = LocalDateTime.of(begin, LocalTime.MIN);//  开始时间为为30天前的00:00:00
        LocalDateTime endTime = LocalDateTime.of(end, LocalTime.MAX);//  结束时间为为昨天的23:59:59.99
        BusinessDataVO businessData = workspaceService.getBusinessData(beginTime, endTime);
        //2:通过POI将数据写入到Excel文件中
        //通过类加载器从类路径中加载资源文件，并返回一个 InputStream。 参数 "template/运营数据报表模板.xlsx" 是资源文件的路径，表示：
        //文件位于类路径下的 template 目录中。
        //文件名为 运营数据报表模板.xlsx。
        //该路径是相对于 src/main/resources的路径。
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/运营数据报表模板.xlsx");
        try {
            // 基于模板文件创建一个新的Excel文件
            // 使用 Apache POI 的 XSSFWorkbook 类加载模板文件（XLSX 格式）
            XSSFWorkbook excel = new XSSFWorkbook(inputStream);

            // 获取表格文件的 sheet1 页
            // 通过名称获取名为 "Sheet1" 的工作表（sheet）
            XSSFSheet sheet = excel.getSheet("Sheet1");
            // 填充数据 -- 时间范围
            // 获取第1行（索引从0开始），第1列（B列），设置时间范围文本
            sheet.getRow(1).getCell(1).setCellValue("时间：" + begin + "至" + end);
            // 获得第4行（索引为3）
            // 获取第4行数据（用于填充总体营业数据）
            XSSFRow row = sheet.getRow(3);
            // 设置第4行第3列（C列）的值为营业额（turnover）
            row.getCell(2).setCellValue(businessData.getTurnover());
            // 设置第4行第5列（E列）的值为有效订单数（validOrderCount）
            row.getCell(4).setCellValue(businessData.getValidOrderCount());
            // 设置第4行第7列（G列）的值为订单完成率（orderCompletionRate）
            row.getCell(6).setCellValue(businessData.getOrderCompletionRate());
            // 获得第5行（索引为4）
            // 获取第5行数据（用于填充其他营业数据）
            row = sheet.getRow(4);
            // 设置第5行第3列（C列）的值为有效订单数（validOrderCount）
            row.getCell(2).setCellValue(businessData.getValidOrderCount());
            // 设置第5行第5列（E列）的值为平均客单价（unitPrice）
            row.getCell(4).setCellValue(businessData.getUnitPrice());
            // 填充明细数据（每日营业数据）
            // 循环30天，从 begin 开始每天加1天，填充每天的数据
            //填充明细数
            for (int i = 0; i < 30; i++){
                // 获取第 i 天的日期
                LocalDate date = begin.plusDays(i); // 例如：第0天是 begin，第1天是 begin + 1 天
                // 查询某一天的营业数据
                // 调用业务方法，获取当天的开始时间和结束时间对应的营业数据
                businessData = workspaceService.getBusinessData(
                        LocalDateTime.of(date, LocalTime.MIN),  // 当天的开始时间
                        LocalDateTime.of(date, LocalTime.MAX)   // 当天的结束时间
                );
                // 填充数据从第8行（索引为7）开始
                // 每次循环获取对应的行（第7 + i 行）
                row = sheet.getRow(7 + i);
                if(row == null){
                    row = sheet.createRow(7 + i);
                }
                // 设置第 i+8 行第2列（B列）为日期字符串（如 "2025-04-05"）
                row.getCell(1).setCellValue(date.toString());
                log.info("查询{}营业数据:{}",date,businessData);
                // 设置第 i+8 行第3列（C列）为当天的营业额
                row.getCell(2).setCellValue(businessData.getTurnover());
                // 设置第 i+8 行第4列（D列）为当天的有效订单数
                row.getCell(3).setCellValue(businessData.getValidOrderCount());
                // 设置第 i+8 行第5列（E列）为当天的订单完成率
                row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
                // 设置第 i+8 行第6列（F列）为当天的平均客单价
                row.getCell(5).setCellValue(businessData.getUnitPrice());
                // 设置第 i+8 行第7列（G列）为当天新增用户数
                row.getCell(6).setCellValue(businessData.getNewUsers());
            }
            //3.通过输出流将Excel文件下载到客户端浏览器
            ServletOutputStream outputStream = response.getOutputStream();//  获取输出流
            excel.write(outputStream);//  将Excel文件写入输出流
            //  关闭流
            outputStream.close();
            excel.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

}
